﻿using System;
using System.Linq;
using System.Threading.Tasks;
using StackExchange.Redis;

namespace xBei.Redis.Extension {
    partial class RedisClient {
        #region LIST
        /// <summary>
        /// 返回List的长度
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public long ListLength(string Key) => getDatabase().ListLength(GetKey(Key));
        /// <summary>
        /// 返回List的长度
        /// </summary>
        /// <param name="Key"></param>
        /// <returns></returns>
        public async Task<long> ListLengthAsync(string Key) => await (await GetDatabaseAsync()).ListLengthAsync(GetKey(Key));
        /// <summary>
        /// 读取列表（List）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Page"></param>
        /// <param name="Limit"></param>
        /// <returns></returns>
        public PageSet<RedisValue> GetList(string Key, int Page = 1, int Limit = 10)
            => GetListAsync(Key, Page, Limit).Result;
        /// <summary>
        /// 读取列表（List）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Page"></param>
        /// <param name="Limit"></param>
        /// <returns></returns>
        public async Task<PageSet<RedisValue>> GetListAsync(string Key, int Page = 1, int Limit = 10) {
            if (Page < 1) {
                throw new ArgumentOutOfRangeException(nameof(Page), "不能小于1");
            }
            if (Limit < 1) {
                throw new ArgumentOutOfRangeException(nameof(Limit), "不能小于1");
            }
            var db = await GetDatabaseAsync();
            var redisKey = GetKey(Key);
            var total = await db.ListLengthAsync(redisKey);
            var totalPage = (int)Math.Ceiling((double)total / Limit);
            if (Page > totalPage) Page = totalPage;
            var start = (Page - 1) * Limit;
            var end = Page * Limit - 1;
            var list = await db.ListRangeAsync(redisKey, start, end);
            return new PageSet<RedisValue> {
                Items = list,
                Page = Page,
                Limit = Limit,
                Total = total,
                PageCount = totalPage
            };
        }
        /// <summary>
        /// 读取列表（List），列表的所有值都是T类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Page"></param>
        /// <param name="Limit"></param>
        /// <returns></returns>
        public PageSet<T?> GetList<T>(string Key, int Page = 1, int Limit = 10) where T : class, new()
            => GetListAsync<T>(Key, Page, Limit).Result;
        /// <summary>
        /// 读取列表（List），列表的所有值都是T类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Page"></param>
        /// <param name="Limit"></param>
        /// <returns></returns>
        public async Task<PageSet<T?>> GetListAsync<T>(string Key, int Page = 1, int Limit = 10) where T : class, new() {
            var result = await GetListAsync(Key, Page, Limit);
            return new PageSet<T?> {
                Items = result.Items
                    .Select(item => (string?)item)
                    .Select(s => s.Deserialize<T>()),
                Page = result.Page,
                Limit = result.Limit,
                Total = result.Total,
                PageCount = result.PageCount,
            };
        }
        /// <summary>
        /// 删除List中的数据
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Start"></param>
        /// <param name="End"></param>
        /// <returns></returns>
        public RedisClient ListRemove(string Key, long Start, long End = 0) {
            if (Start < 0) {
                throw new ArgumentOutOfRangeException(nameof(Start), "必须大于等于0");
            }
            if (End < 0) {
                throw new ArgumentOutOfRangeException(nameof(End), "必须大于等于0");
            }
            if (End == 0) {
                End = Start;
            }
            if (End < Start) {
                (Start, End) = (End, Start);
            }
            var redisKey = GetKey(Key);
            var db = getDatabase();
            var total = db.ListLength(redisKey);
            if (Start >= total) return this;
            if (End >= total) End = total - 1;
            if (Start == 0) {
                for (var i = 0; i <= End; i++) {
                    db.ListLeftPop(redisKey, CommandFlags.FireAndForget);
                }
                return this;
            }
            if (End == total - 1) {
                for (var i = Start; i <= End; i++) {
                    db.ListRightPop(redisKey, CommandFlags.FireAndForget);
                }
                return this;
            }

            var list = db.ListRange(redisKey, Start, End);
            var flag = Start < total / 2;
            var listCount = list.Length;
            for (var i = 0; i < listCount; i++) {
                if (flag) {
                    db.ListRemove(redisKey, list[i], 1, CommandFlags.FireAndForget);
                } else {
                    db.ListRemove(redisKey, list[i], -1, CommandFlags.FireAndForget);
                }
            }
            return this;
        }
        /// <summary>
        /// 设置列表的某项（Index）值
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Index"></param>
        /// <param name="Value"></param>
        /// <param name="throwException"></param>
        /// <returns></returns>
        public RedisClient ListSetValue(string Key, long Index, RedisValue Value, bool throwException = false) {
            if (Index < 0) {
                return throwException ? throw new ArgumentOutOfRangeException(nameof(Index), "不能小于0") : this;
            }
            if (!Exists(Key)) {
                return throwException ? throw new Exception($"{nameof(Key)}不存在") : this;
            }
            var db = getDatabase();
            var redisKey = GetKey(Key);
            var total = db.ListLength(redisKey);
            if (Index >= total) {
                return throwException ? throw new ArgumentOutOfRangeException(nameof(Index), $"必须小于{total}") : this;
            }
            db.ListSetByIndex(redisKey, Index, Value, CommandFlags.FireAndForget);
            return this;
        }
        /// <summary>
        /// 设置列表的某项（Index）值
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Index"></param>
        /// <param name="Value"></param>
        /// <param name="throwException"></param>
        /// <returns></returns>
        public RedisClient ListSetValue(string Key, long Index, DateTime Value, bool throwException = false)
            => ListSetValue(Key, Index, Value.Ticks, throwException);
        /// <summary>
        /// 设置列表的某项（Index）值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Index"></param>
        /// <param name="Value"></param>
        /// <param name="throwException"></param>
        /// <returns></returns>
        public RedisClient ListSetValue<T>(string Key, long Index, T Value, bool throwException = false) where T : class, new()
            => ListSetValue(Key, Index, Value.Serialize(), throwException);
        /// <summary>
        /// 获取列表的某项（Index）值
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Index"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public RedisValue ListGet(string Key, long Index, RedisValue dv) {
            var redisKey = GetKey(Key);
            var db = getDatabase();
            if (!db.KeyExists(redisKey)) {
                return dv;
            }
            var result = db.ListGetByIndex(redisKey, Index);
            return result == RedisValue.Null ? dv : result;
        }
        /// <summary>
        /// 获取列表的某项（Index）值
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Index"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public DateTime ListGet(string Key, long Index, DateTime dv) {
            var redisKey = GetKey(Key);
            var db = getDatabase();
            if (!db.KeyExists(redisKey)) {
                return dv;
            }
            var result = db.ListGetByIndex(redisKey, Index);
            return result == RedisValue.Null ? dv : new DateTime((long)result);
        }
        /// <summary>
        /// 获取列表的某项（Index）值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Index"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public T ListGet<T>(string Key, long Index, T dv) where T : class, new() {
            var redisKey = GetKey(Key);
            var db = getDatabase();
            if (!db.KeyExists(redisKey)) {
                return dv;
            }
            var result = db.ListGetByIndex(redisKey, Index);
            return result == RedisValue.Null ? dv : ((string?)result).Deserialize<T>() ?? dv;
        }
        #region Queue 先进先去
        /// <summary>
        /// 入队
        /// ListRightPush
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public RedisClient Enqueue(string Key, RedisValue Value, int Seconds = 0)
            => EnqueueAsync(Key, Value, Seconds).Result;
        /// <summary>
        /// 入队
        /// ListRightPush
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public async Task<RedisClient> EnqueueAsync(string Key, RedisValue Value, int Seconds = 0) {
            await (await GetDatabaseAsync()).ListRightPushAsync(GetKey(Key), Value, flags: CommandFlags.FireAndForget);
            if (Seconds > 0)
                return await ExpireAsync(GetKey(Key), Seconds);
            return this;
        }
        /// <summary>
        /// 入队
        /// ListRightPush
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public RedisClient Enqueue(string Key, DateTime Value, int Seconds = 0)
            => Enqueue(Key, Value.Ticks, Seconds);
        /// <summary>
        /// 入队
        /// ListRightPush
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public async Task<RedisClient> EnqueueAsync(string Key, DateTime Value, int Seconds = 0)
            => await EnqueueAsync(Key, Value.Ticks, Seconds);
        /// <summary>
        /// 入队
        /// ListRightPush
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public RedisClient Enqueue<T>(string Key, T Value, int Seconds = 0) where T : class, new()
            => Enqueue(Key, Value.Serialize(), Seconds);
        /// <summary>
        /// 入队
        /// ListRightPush
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds">如果指定了超时时间，每次插入数据都会重置超时时间
        /// 同一个Key，如果第一次入队没有指定超时时间，之后的入队操作有指定超时时间，那么这个Key的超时时间就是按最后一次操作来。
        /// 同一个Key，如果在任何（除了最后）一次入队的时候指定了超时时间，但是最后一次没有指定，那么这个Key的超时时间是按“最后一次”有指定超时的入队操作来计算的。
        /// </param>
        /// <returns></returns>
        public async Task<RedisClient> EnqueueAsync<T>(string Key, T Value, int Seconds = 0) where T : class, new()
            => await EnqueueAsync(Key, Value.Serialize(), Seconds);
        /// <summary>
        /// 出队（先进先出）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public RedisValue Dequeue(string Key, RedisValue dv)
            => Exists(Key)
                ? getDatabase().ListLeftPop(GetKey(Key))
                : dv;
        /// <summary>
        /// 出队（先进先出）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<RedisValue> DequeueAsync(string Key, RedisValue dv)
            => await ExistsAsync(Key)
                ? await (await GetDatabaseAsync()).ListLeftPopAsync(GetKey(Key))
                : dv;
        /// <summary>
        /// 出队（先进先出）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<RedisValue> DequeueV2Async(string Key, RedisValue dv) {
            var r = await (await GetDatabaseAsync()).ListLeftPopAsync(GetKey(Key));
            return r.IsNullOrEmpty ? dv : r;
        }
        /// <summary>
        /// 出队（先进先出）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public DateTime Dequeue(string Key, DateTime dv)
            => Exists(Key)
                ? new DateTime((long)(getDatabase()).ListLeftPop(GetKey(Key)))
                : dv;
        /// <summary>
        /// 出队（先进先出）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<DateTime> DequeueAsync(string Key, DateTime dv)
            => await ExistsAsync(Key)
                ? new DateTime((long)await (await GetDatabaseAsync()).ListLeftPopAsync(GetKey(Key)))
                : dv;
        /// <summary>
        /// 出队（先进先出）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public T? Dequeue<T>(string Key, T? dv = null) where T : class, new()
            => Exists(Key)
                ? ((string?)getDatabase().ListLeftPop(GetKey(Key))).Deserialize<T>()
                : dv;
        /// <summary>
        /// 出队（先进先出）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<T?> DequeueAsync<T>(string Key, T? dv = null) where T : class, new()
            => await ExistsAsync(Key)
                ? ((string?)await (await GetDatabaseAsync()).ListLeftPopAsync(GetKey(Key))).Deserialize<T>()
                : dv;
        #endregion
        #region Stack 先进后出
        /// <summary>
        /// 入栈
        /// ListLeftPush
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public RedisClient Push(string Key, RedisValue Value, int Seconds = 0)
            => PushAsync(Key, Value, Seconds).Result;
        /// <summary>
        /// 入栈
        /// ListLeftPush
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public async Task<RedisClient> PushAsync(string Key, RedisValue Value, int Seconds = 0) {
            await (await GetDatabaseAsync()).ListLeftPushAsync(GetKey(Key), Value, flags: CommandFlags.FireAndForget);
            if (Seconds > 0)
                return await ExpireAsync(GetKey(Key), Seconds);
            return this;
        }
        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public RedisClient Push(string Key, DateTime Value, int Seconds = 0)
            => PushAsync(Key, Value.Ticks, Seconds).Result;
        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public async Task<RedisClient> PushAsync(string Key, DateTime Value, int Seconds = 0)
            => await PushAsync(Key, Value.Ticks, Seconds);
        /// <summary>
        /// 入栈
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public RedisClient Push<T>(string Key, T Value, int Seconds = 0) where T : class, new()
            => PushAsync<T>(Key, Value, Seconds).Result;
        /// <summary>
        /// 入栈
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="Value"></param>
        /// <param name="Seconds"></param>
        /// <returns></returns>
        public async Task<RedisClient> PushAsync<T>(string Key, T Value, int Seconds = 0) where T : class, new()
            => await PushAsync(Key, Value.Serialize(), Seconds);
        /// <summary>
        /// 出栈（ListRightPop）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public RedisValue Pop(string Key, RedisValue dv)
            => PopAsync(Key, dv).Result;
        /// <summary>
        /// 出栈（ListRightPop）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<RedisValue> PopAsync(string Key, RedisValue dv)
            => await ExistsAsync(Key)
                ? await (await GetDatabaseAsync()).ListRightPopAsync(GetKey(Key))
                : dv;
        /// <summary>
        /// 出栈（ListRightPop）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public DateTime Pop(string Key, DateTime dv)
            => PopAsync(Key, dv).Result;
        /// <summary>
        /// 出栈（ListRightPop）
        /// </summary>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<DateTime> PopAsync(string Key, DateTime dv)
            => await ExistsAsync(Key)
                ? new DateTime((long)await (await GetDatabaseAsync()).ListRightPopAsync(GetKey(Key)))
                : dv;
        /// <summary>
        /// 出栈（ListRightPop）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public T? Pop<T>(string Key, T? dv = null) where T : class, new()
             => PopAsync(Key, dv).Result;
        /// <summary>
        /// 出栈（ListRightPop）
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="Key"></param>
        /// <param name="dv"></param>
        /// <returns></returns>
        public async Task<T?> PopAsync<T>(string Key, T? dv = null) where T : class, new()
            => await ExistsAsync(Key)
                ? ((string?)await (await GetDatabaseAsync()).ListRightPopAsync(GetKey(Key))).Deserialize<T>()
                : dv;
        #endregion
        #endregion
    }
}
